package ua.edu.nuos.jeetraining2012.cms.model;

import org.slf4j.Logger;
import ua.edu.nuos.jeetraining2012.cms.entity.CourseEntity;
import ua.edu.nuos.jeetraining2012.cms.model.exception.DAOException;

import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.Arrays;
import java.util.List;

/**
 * DAO layer for course table.
 *
 * @author Victor N. Ganichev victor.ganichev@gmail.com
 * @version 1.0 Date: 01.06.13 Time: 23:34
 * @since 1.0
 */
@Local
@Stateless
public class CourseDAO {

    /**
     * Define EntityManager.
     */
    @PersistenceContext
    private EntityManager em;

    /**
     * Define logger.
     */
    final private static Logger logger = org.slf4j.LoggerFactory.getLogger(CourseDAO.class);


    // Constructors

    /**
     * Default constructor.
     */
    public CourseDAO() {
        logger.debug("CourseDAO invocation.");
    }

    /**
     * Constructor with predefined EntityManager.
     * Use this one for testing purposes.
     *
     * @param em Instance of EntityManager.
     */
    protected CourseDAO(EntityManager em) {
        logger.debug("CourseDAO invocation with predefined EntityManager {}", em);
        setEm(em);
    }

    // Getters & Setters
    protected EntityManager getEm() {
        return em;
    }

    private void setEm(EntityManager em) {
        this.em = em;
    }

    // Custom methods

    /**
     * Find all courses.
     * <p/>
     * Use this method to find out all available courses.
     * In case of success, List of CourseEntity objects will be returned.
     * In some other cases (when parent exception caught), DAOException can be produced.
     *
     * @return List of courses.
     * @throws DAOException Thrown in cases, when parent exception was thrown.
     */
    @SuppressWarnings("unchecked")
    public List<CourseEntity> findAll() throws DAOException {
        logger.debug("Method findAll() invocation.");
        List<CourseEntity> courses;

        try {
            Query q = em.createQuery("SELECT c from CourseEntity c");
            logger.debug("Trying to findAll with query: {}", q.toString());

            courses = q.getResultList();
            logger.debug("Fetching result... Get {} items in list.", courses.size());

        } catch (IllegalArgumentException e) {
            logger.warn("Query string is invalid. Error message: {} \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("findAll() - finished with DAOException.");
            throw new DAOException("Unable to find all courses. Error message: " + e.getMessage());
        } catch (Exception e) {
            logger.warn("Error, parent exception exceeded. Message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            throw new DAOException("Unable to find all courses. Error message: " + e.getMessage());
        }

        logger.debug("findAll() - finished.");
        return courses;
    }

    /**
     * Find by id value.
     * Search for an entity of the specified class by his id.
     * If the entity instance is contained in the persistence context,
     * it is returned from there.
     * @param id value id
     * @return the found entity instance or null if the entity does not exist
     * @throws DAOException
     */
    public CourseEntity findById(long id) throws DAOException {
        logger.debug("findByUserId() - start()");
        if ((id < 0)) {
            logger.warn("Incorrect id value");
            logger.debug("findByUserId() - finished with IllegalArgumentException");
            throw new IllegalArgumentException("Can't find record with id - " + id);
        }

        CourseEntity course;
        try {
            course = em.find(CourseEntity.class, id);
        } catch (Exception e) {
            logger.warn("Error!. Can't find course. Message: " + e.getMessage() + "\nTrace:\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("findByUserId() - finished with DAOException");
            throw new DAOException("Unable to find user. Error message: " + e.getMessage());
        }

        logger.debug("findByUserId() - finished.");
        return course;
    }

    /**
     * Method return the number of table entries
     * @return Integer, number of elements in table
     * @throws DAOException
     */
    public Integer getSize() throws DAOException {
        logger.debug("Method getSize() invocation.");
        Integer size;

        try {
            Query q = em.createQuery("SELECT count(c) FROM CourseEntity c");
            size = (Integer) q.getSingleResult();
            logger.debug("Table course consist of" + size + "elements");
        } catch (IllegalArgumentException e) {
            logger.warn("Query string is invalid. Error message: {} \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("getSize() - finished with DAOException.");
            throw new DAOException("Unable to find all courses. Error message: " + e.getMessage());
        } catch (Exception e) {
            logger.warn("Error, parent exception exceeded. Message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            throw new DAOException("Unable to getSize of courses. Error message: " + e.getMessage());
        }
        return size;
    }

    /**
     * Method checks that table courses is empty
     * @return true, if empty
     * @return false, otherwise
     * @throws DAOException
     */
    public boolean isEmpty() throws DAOException {
        logger.debug("Method isEmpty() invocation.");
        return (getSize() == 0) ? true : false;
    }

    /**
     * Create new course DB record.
     * In case of success id of inserted course will return. Either null.
     * If sent entity is null, IllegalArgumentException will be thrown.
     * If got some parent exception during persist process, DAOException will be thrown.
     *
     * @param entity                        CourseEntity object.
     * @return                              ID of inserted course.
     * @throws IllegalArgumentException     If entity is null.
     * @throws DAOException                 If any parent exception appeared.
     */
    public Long create(CourseEntity entity) throws IllegalArgumentException, DAOException {
        logger.debug("create() - started.");

        if (entity == null) {
            logger.error("Persistable entity can't be {}", entity);
            logger.debug("create() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Persists entity can't be null.");
        }

        Long lastInsertId = null;
        logger.debug("Adding entity {} to DB.", entity);
        try {
            em.persist(entity);
            em.flush();
            lastInsertId = entity.getId();
            logger.info("Entity successful persists into DB. Entity ID is {}", lastInsertId);
        } catch (Exception e) {
            logger.error("Can't persists entity into DB. Error message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("create() - finished with DAOException.");
            throw new DAOException("Can't persists entity into DB. Error message: " + e.getMessage());
        }

        logger.debug("create() - finished.");
        return lastInsertId;
    }

    /**
     * Update course record in DB.
     * In case of success True will be returned, either False.
     * If entity is null, IllegalArgumentException will be thrown.
     * If any parent Exception appeared, DAOException will be thrown.
     *
     * @param entity                        CourseEntity object.
     * @return                              True if record updated successful.
     * @throws IllegalArgumentException     If entity in null.
     * @throws DAOException                 If any parent exception appeared.
     */
    public boolean update(CourseEntity entity) throws IllegalArgumentException, DAOException {
        logger.debug("update() - started.");

        if (entity == null) {
            logger.error("update() Updatable course can't be {}", entity);
            logger.debug("create() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Updateble course can't be null.");
        }

        boolean isUpdated = false;
        logger.debug("update() Update course {} in DB", entity);
        try {
            em.merge(entity);
            isUpdated = true;
            logger.info("update() Course successful updated.");
        } catch (Exception e) {
            logger.error("update() Can't update courses, message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("update() - finished with DAOException.");
            throw new DAOException("Can't update course. Error message: " + e.getMessage());
        }

        logger.debug("update() - finished.");
        return isUpdated;
    }

    /**
     * Delete record from DB.
     * In case of success, true will be returned.
     * If entity is null, IllegalArgumentException will be thrown.
     * If any parent Exception appeared, DAOException will be thrown.
     *
     * @param entity                        CourseEntity object.
     * @return                              True if record removed from DB.
     * @throws IllegalArgumentException     If entity is null.
     * @throws DAOException                 If any parent exception appeared.
     */
    public boolean delete(CourseEntity entity) throws DAOException, IllegalArgumentException {
        logger.debug("delete() - started.");

        if (entity == null) {
            logger.error("delete() Deletable course can't be {}", entity);
            logger.debug("delete() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Deletable course can't be null.");
        }

        boolean isDeleted = false;
        try {
            CourseEntity courseEntity = em.find(CourseEntity.class,entity.getId());
            em.remove(courseEntity);
            isDeleted = true;
            logger.info("delete() Course successful deleted from DB.");
        } catch (Exception e) {
            logger.error("delete() Can't delete course from DB. Error message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("delete() - finished with DAOException.");
            throw new DAOException("Can't delete coruse from DB. Error message: " + e.getMessage());
        }

        logger.debug("delete() - finished.");
        return isDeleted;
    }

}
